#include <iostream>
#include <thread>
#include <fstream>
#include <memory>
#include <vector>

#include "ftpserver/IoFile.h"

#define BOOST_TEST_MODULE IoFileTest
#define BOOST_TEST_MAIN
#define BOOST_TEST_DYN_LINK

#include <boost/test/unit_test.hpp>

using namespace std;
using namespace ftp::filesystem;

BOOST_AUTO_TEST_CASE(testIoFileOpenAndClose)
{
    bool res(false);
    std::string path = "test.text";

    IoFile file(path, std::ios::out);
    res = file.fileStream_.is_open();
    BOOST_CHECK_EQUAL(res, true);
    file.fileStream_.close();
    res = file.fileStream_.is_open();
    BOOST_CHECK_EQUAL(res, false);
}

BOOST_AUTO_TEST_CASE(testIoFileWrite)
{
    bool res(false);
    std::string path = "test.text";
    { // 1 user write
        IoFile file1(path, std::ios::out);
        res = file1.fileStream_.is_open();
        BOOST_CHECK_EQUAL(res, true);
        int userId = 1;
        std::string s = "writer" + std::to_string(userId) + "abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789\n";
        file1.fileStream_.write(s.c_str(), static_cast<ssize_t>(s.size()));
    }

    { // 5 users write at the same time
        IoFile file1(path, std::ios::out);
        res = file1.fileStream_.is_open();
        BOOST_CHECK_EQUAL(res, true);
        int userId = 1;
        std::string s = "writer" + std::to_string(userId) + ": abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789\n";
        file1.fileStream_.write(s.c_str(), static_cast<ssize_t>(s.size()));

        ++userId;
        IoFile file2(path, std::ios::out);
        res = file2.fileStream_.is_open();
        BOOST_CHECK_EQUAL(res, false);
        s = "writer" + std::to_string(userId) + "abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789\n";
        file2.fileStream_.write(s.c_str(), static_cast<ssize_t>(s.size()));

        ++userId;
        IoFile file3(path, std::ios::out);
        res = file3.fileStream_.is_open();
        BOOST_CHECK_EQUAL(res, false);
        s = "writer" + std::to_string(userId) + "abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789\n";
        file3.fileStream_.write(s.c_str(), static_cast<ssize_t>(s.size()));

        ++userId;
        IoFile file4(path, std::ios::out);
        res = file4.fileStream_.is_open();
        BOOST_CHECK_EQUAL(res, false);
        s = "writer" + std::to_string(userId) + "abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789\n";
        file4.fileStream_.write(s.c_str(), static_cast<ssize_t>(s.size()));

        ++userId;
        IoFile file5(path, std::ios::out);
        res = file5.fileStream_.is_open();
        BOOST_CHECK_EQUAL(res, false);
        s = "writer" + std::to_string(userId) + "abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789\n";
        file5.fileStream_.write(s.c_str(), static_cast<ssize_t>(s.size()));
    }
}

BOOST_AUTO_TEST_CASE(testIoFileRead)
{
    bool res(false);
    std::string path = "test.text";
    const std::string content = "abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789\n";

    { // write content for following reading
        IoFile file(path, std::ios::out);
        res = file.fileStream_.is_open();
        file.fileStream_.write(content.c_str(), static_cast<ssize_t>(content.size()));
        file.fileStream_.close();
    }

    { // 1 user read
        char buf[1024];
        IoFile file(path, std::ios::in);
        res = file.fileStream_.is_open();
        BOOST_CHECK_EQUAL(res, true);
        file.fileStream_.read(buf, static_cast<ssize_t>(sizeof(buf)));
        // write terminated NUL-byte C-style string
        buf[file.fileStream_.gcount()] = '\0';
        BOOST_CHECK_EQUAL(std::strcmp(buf, content.c_str()), 0);
    }

    {
        // 5 user read at the same time
        std::array<char[1024], 5> bufs{};
        size_t userId = 0;

        IoFile file1(path, std::ios::in);
        res = file1.fileStream_.is_open();
        BOOST_CHECK_EQUAL(res, true);
        file1.fileStream_.read(bufs[userId], static_cast<ssize_t>(sizeof(bufs[userId])));
        bufs[userId][file1.fileStream_.gcount()] = '\0';
        BOOST_CHECK_EQUAL(std::strcmp(bufs[userId], content.c_str()), 0);

        ++userId;
        IoFile file2(path, std::ios::in);
        res = file2.fileStream_.is_open();
        BOOST_CHECK_EQUAL(res, true);
        file2.fileStream_.read(bufs[userId], static_cast<ssize_t>(sizeof(bufs[userId])));
        bufs[userId][file2.fileStream_.gcount()] = '\0';
        BOOST_CHECK_EQUAL(std::strcmp(bufs[userId], content.c_str()), 0);

        ++userId;
        IoFile file3(path, std::ios::in);
        res = file3.fileStream_.is_open();
        BOOST_CHECK_EQUAL(res, true);
        file3.fileStream_.read(bufs[userId], static_cast<ssize_t>(sizeof(bufs[userId])));
        bufs[userId][file2.fileStream_.gcount()] = '\0';
        BOOST_CHECK_EQUAL(std::strcmp(bufs[userId], content.c_str()), 0);

        ++userId;
        IoFile file4(path, std::ios::in);
        res = file4.fileStream_.is_open();
        BOOST_CHECK_EQUAL(res, true);
        file4.fileStream_.read(bufs[userId], static_cast<ssize_t>(sizeof(bufs[userId])));
        bufs[userId][file4.fileStream_.gcount()] = '\0';
        BOOST_CHECK_EQUAL(std::strcmp(bufs[userId], content.c_str()), 0);

        ++userId;
        IoFile file5(path, std::ios::in);
        res = file5.fileStream_.is_open();
        BOOST_CHECK_EQUAL(res, true);
        file5.fileStream_.read(bufs[userId], static_cast<ssize_t>(sizeof(bufs[userId])));
        bufs[userId][file5.fileStream_.gcount()] = '\0';
        BOOST_CHECK_EQUAL(std::strcmp(bufs[userId], content.c_str()), 0);
    }
}

BOOST_AUTO_TEST_CASE(testIoFileWriteAndRead)
{
    bool res(false);
    std::string path = "test.text";
    const std::string content = "abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789\n";

    {
        // write
        IoFile writeFile(path, std::ios::out);
        res = writeFile.fileStream_.is_open();
        BOOST_CHECK_EQUAL(res, true);
        writeFile.fileStream_.write(content.c_str(), static_cast<ssize_t>(content.size()));

        // read
        char buf[1024];
        IoFile readFile(path, std::ios::in);
        res = readFile.fileStream_.is_open();
        BOOST_CHECK_EQUAL(res, false);
        readFile.fileStream_.read(buf, static_cast<ssize_t>(sizeof(buf)));
        buf[readFile.fileStream_.gcount()] = '\0';
        BOOST_CHECK(std::strcmp(buf, content.c_str()) != 0);
    }

    {
        // read
        char buf[1024];
        IoFile readFile(path, std::ios::in);
        res = readFile.fileStream_.is_open();
        BOOST_CHECK_EQUAL(res, true);
        readFile.fileStream_.read(buf, static_cast<ssize_t>(sizeof(buf)));
        buf[readFile.fileStream_.gcount()] = '\0';
        BOOST_CHECK(std::strcmp(buf, content.c_str()) == 0);

        // write
        IoFile writeFile(path, std::ios::out);
        res = writeFile.fileStream_.is_open();
        BOOST_CHECK_EQUAL(res, false);
    }
}
